package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.model.OrderRecode;
import com.atguigu.gmall.activity.model.SeckillGoods;
import com.atguigu.gmall.activity.model.UserRecode;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.rabbit.config.MqConst;
import com.atguigu.gmall.common.rabbit.service.RabbitService;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.DateUtil;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.order.client.OrderFeignClient;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 业务实现类
 *
 * @author atguigu
 * @since 2023-05-13
 */
@Service
public class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private Cache<String, String> seckillCache;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private OrderFeignClient orderFeignClient;

    /**
     * 查询Redis中当日秒杀商品列表
     *
     * @return
     */
    @Override
    public List<SeckillGoods> getSeckillGoodsList() {
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        return hashOps.values();
    }

    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        return hashOps.get(skuId.toString());
    }


    /**
     * 点击秒杀按钮前,为当前登录用户生成购买指定商品抢购码,避免非法抢购
     *
     * @param userId 用户ID
     * @param skuId  秒杀商品ID
     * @return
     */
    @Override
    public String getSeckillBuyCode(String userId, Long skuId) {
        //1.验证本地缓存中商品状态位 是否为"1"
        String state = seckillCache.getIfPresent(skuId.toString());
        if (StringUtils.isBlank(state)) {
            throw new RuntimeException(ResultCodeEnum.ILLEGAL_REQUEST.getMessage());
        }
        if ("0".equals(state)) {
            throw new RuntimeException(ResultCodeEnum.SECKILL_END.getMessage());
        }
        //2.验证商品是否在销售时间
        if ("1".equals(state)) {
            String seckillGoodsKey = RedisConst.SECKILL_GOODS;
            BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(seckillGoodsKey);
            SeckillGoods seckillGoods = hashOps.get(skuId.toString());
            Date now = new Date();
            if (seckillGoods != null && DateUtil.dateCompare(seckillGoods.getStartTime(), now) && DateUtil.dateCompare(now, seckillGoods.getEndTime())) {
                //3.为当前用户购买商品生成抢购码 todo 存入redis方便验证
                String buyCode = MD5.encrypt(userId + skuId);
                return buyCode;
            }
        }
        throw new RuntimeException(ResultCodeEnum.SECKILL_ILLEGAL.getMessage());
    }


    /**
     * 秒杀请求入队-将用户购买意向发送消息队列
     *
     * @param userId      用户ID
     * @param skuId       商品ID
     * @param userBuyCode 抢购码
     * @return
     */
    @Override
    public Result seckillRequestToQueue(String userId, Long skuId, String userBuyCode) {
        //1.验证用户提交抢购码是否正确
        String buyCode = MD5.encrypt(userId + skuId);
        if (!userBuyCode.equals(buyCode)) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }
        //2.验证本地缓存中商品状态
        String status = seckillCache.getIfPresent(skuId.toString());
        if (StringUtils.isBlank(status) || "0".equals(status)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }
        //3.构建用户秒杀商品购买意向发送到消息队列
        if ("1".equals(status)) {
            //3.1 购买用户购买意向信息对象(包含用户ID,商品ID,购买数量)
            UserRecode userRecode = new UserRecode();
            userRecode.setUserId(userId);
            userRecode.setSkuId(skuId);
            //3.2 发送到消息队列
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);
            return Result.ok();
        }
        return null;
    }


    /**
     * 监听秒杀队列中购买意向,完成秒杀核心业务-获取抢购资格
     *
     * @param userRecode 用户购买意向
     */
    @Override
    public void processSeckillRequest(UserRecode userRecode) {
        //1.从本地缓存中获取商品状态1:执行执行 0:结束
        String status = seckillCache.getIfPresent(userRecode.getSkuId().toString());
        if (StringUtils.isBlank(status) || "0".equals(status)) {
            return;
        }
        //2.判断用户是否重复排队
        String ifReQueueKey = RedisConst.SECKILL_USER + userRecode.getUserId() + ":" + userRecode.getSkuId() + ":requeue";
        //todo 是否重复排队key的失效时间 动态计算  距离秒杀结束前
        Boolean ifRequeue = redisTemplate.opsForValue().setIfAbsent(ifReQueueKey, userRecode.getSkuId().toString(), 1, TimeUnit.HOURS);
        if (!ifRequeue) {
            return;
        }
        //3.尝试从Redis中商品库存List结构中弹出数据 是否能够从List库存结构中弹出数据
        String seckillStockKey = RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId();
        BoundListOperations<String, String> listOps = redisTemplate.boundListOps(seckillStockKey);
        String hashStock = listOps.rightPop();
        // 3.1 失败:更新所有节点本地缓存
        if (StringUtils.isBlank(hashStock)) {
            //发送消息到Redis 发布订阅话题
            redisTemplate.convertAndSend("seckillpush", userRecode.getSkuId() + ":0");
            return;
        }
        // 3.2 获取数据成功:获取到秒杀资格
        //4.为用户生成临时订单 - 存入Redis
        OrderRecode tempOrder = new OrderRecode();
        tempOrder.setUserId(userRecode.getUserId());
        tempOrder.setNum(1);
        //临时订单的标识
        String orderNoStr = UUID.randomUUID().toString().replaceAll("-", "");
        tempOrder.setOrderStr(orderNoStr);
        tempOrder.setSeckillGoods(this.getSeckillGoods(userRecode.getSkuId()));
        String tempOrderKey = RedisConst.SECKILL_ORDERS;
        String hashKey = userRecode.getUserId() + ":" + userRecode.getSkuId();
        redisTemplate.opsForHash().put(tempOrderKey, hashKey, tempOrder);
        //todo 发送延迟消息(5分钟) 判断用户是否提交订单并支付

        // 5. 发送消息到MQ更新库存数量
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK, MqConst.ROUTING_SECKILL_STOCK, userRecode.getSkuId());
    }

    /**
     * 扣减秒杀商品库存
     *
     * @param skuId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deductSecKillStock(Long skuId) {
        //1.更新Redis中秒杀商品Hash结构中商品库存数量
        //1.1 查询Redis中商品库存List结构获取最新库存数
        String stockListKey = RedisConst.SECKILL_STOCK_PREFIX + skuId;
        BoundListOperations<String, String> listOps = redisTemplate.boundListOps(stockListKey);
        Long stockCount = listOps.size();

        //1.2 查询Redis中秒杀商品信息更新数量
        String seckillGoodsKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(seckillGoodsKey);
        SeckillGoods seckillGoods = hashOps.get(skuId.toString());
        if (seckillGoods != null) {
            seckillGoods.setStockCount(stockCount.intValue());
            hashOps.put(skuId.toString(), seckillGoods);
            //2.更新MySQL数据库中秒杀表中商品库存
            this.updateById(seckillGoods);
        }
    }

    /**
     * 检查秒杀商品结果
     *
     * @param userId
     * @param skuId
     * @return
     */
    @Override
    public Result checkSeckillResult(String userId, Long skuId) {
        //1.判断用户是否在排队-有机会获取秒杀资格
        String ifReQueueKey = RedisConst.SECKILL_USER + userId + ":" + skuId + ":requeue";
        String ifQueueResult = (String) redisTemplate.opsForValue().get(ifReQueueKey);
        if (StringUtils.isNotBlank(ifQueueResult)) {
            //2.判断用户是否生成的秒杀订单-响应218
            String seckillOrderKey = RedisConst.SECKILL_ORDERS_USERS;
            BoundHashOperations<String, String, Long> seckillOrderHashOps = redisTemplate.boundHashOps(seckillOrderKey);
            Boolean flag = seckillOrderHashOps.hasKey(userId + ":" + skuId);
            if (flag) {
                //提交订单 响应订单ID  跟后续支付对接
                Long orderId = seckillOrderHashOps.get(userId + ":" + skuId);
                return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
            }

            //3.判断用户是否产生临时订单-响应215
            String tempOrderKey = RedisConst.SECKILL_ORDERS;
            BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
            flag = tempOrderHashOps.hasKey(userId + ":" + skuId);
            if (flag) {
                //产生临时订单 响应临时订单信息
                OrderRecode orderRecode = tempOrderHashOps.get(userId + ":" + skuId);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }

        }
        //4.如果用户未在排队且本地缓存中商品状态为"0" -响应213
        String state = seckillCache.getIfPresent(skuId.toString());
        if ("0".equals(state)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }
        return Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 汇总秒杀订单确认页面数据
     * ${detailArrayList}:订单明细信息
     * ${totalNum}:订单商品数量
     * ${totalAmount}:总金额
     * ${userAddressList}:收件地址列表
     *
     * @param userId
     * @param skuId
     * @return
     */
    @Override
    public Map<String, Object> seckillOrderTrade(String userId, Long skuId) {
        HashMap<String, Object> mapResult = new HashMap<>();
        //1.从临时订单中获取用户购买商品信息-封装订单明细
        String tempOrderKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
        String hashKey = userId + ":" + skuId;
        Boolean flag = tempOrderHashOps.hasKey(hashKey);
        if (flag) {
            OrderRecode orderRecode = tempOrderHashOps.get(hashKey);
            if (orderRecode != null) {
                //查询到当前用户临时订单中秒杀商品
                SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
                //将秒杀商品对象转为订单明细对象
                OrderDetail orderDetail = new OrderDetail();
                BeanUtils.copyProperties(seckillGoods, orderDetail);
                orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
                orderDetail.setSkuNum(orderRecode.getNum());
                orderDetail.setOrderPrice(seckillGoods.getCostPrice());
                List<OrderDetail> orderDetailList = Arrays.asList(orderDetail);
                mapResult.put("detailArrayList", orderDetailList);
                //2.封装订单商品数量
                mapResult.put("totalNum", orderRecode.getNum());
                //3.封装订单总金额
                OrderInfo orderInfo = new OrderInfo();
                orderInfo.setOrderDetailList(orderDetailList);
                orderInfo.sumTotalAmount();
                mapResult.put("totalAmount", orderInfo.getTotalAmount());
            }
            //4.远程调用用户微服务查询收件地址列表
            List<UserAddress> addressList = userFeignClient.getUserAddressListByUserId(Long.valueOf(userId));
            mapResult.put("userAddressList", addressList);
        }
        return mapResult;
    }

    /**
     * 提交秒杀订单并且保存
     *
     * @param orderInfo
     * @return
     */
    @Override
    public Long submitSeckillOrder(OrderInfo orderInfo) {
        //1.远程调用订单微服务 保存秒杀订单
        Long orderId = orderFeignClient.submitSeckillOrder(orderInfo);
        //2.清理产生当前订单过程中生成临时订单
        String tempOrderKey = RedisConst.SECKILL_ORDERS;
        BoundHashOperations<String, String, OrderRecode> tempOrderHashOps = redisTemplate.boundHashOps(tempOrderKey);
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            Long skuId = orderDetailList.get(0).getSkuId();
            String hashKey = orderInfo.getUserId() + ":" + skuId;
            tempOrderHashOps.delete(hashKey);

            //3.将用户产生秒杀订单保存到Redis
            String seckillOrderKey = RedisConst.SECKILL_ORDERS_USERS;
            BoundHashOperations<String, String, Long> seckillOrderHashOps = redisTemplate.boundHashOps(seckillOrderKey);
            seckillOrderHashOps.put(hashKey, orderId);
        }
        return orderId;
    }

    /**
     * 将当日产生秒杀缓存全部清理
     */
    @Override
    public void processCleanCache() {
        //1.清理分布式缓存
        Set<String> keys = redisTemplate.keys("seckill:" + "*");
        redisTemplate.delete(keys);
        //2.清理本地缓存--TODO问题:只是清理当前节点本地缓存
        seckillCache.invalidateAll();
        //3.TODO解决 发送消息到Redis话题  编写Redis话题监听方法 清理每个节点本地缓存
    }



}
